/*
 * main.c
 *
 *  Created on: 1-nov-2012
 *      Author: dna
 */

#include "stdlib.h"
#include "stdio.h"
#include "string.h"

#include "sys/utsname.h"
#include "sched.h"

#include "thread.h"

#ifndef SCHED_BATCH
#define SCHED_BATCH 3
#endif

#ifndef SCHED_IDLE
#define SCHED_IDLE 5
#endif


#define PARAM1 1
#define PARAM2 2
#define PARAM3 3
#define PARAM4 4
#define PARAM5 5

#define THREADZ 5
#define REPEATER (1024*1024)

/*
 *  0 - impostare routine con I/O e senza (cpu-bound)			check!
 *  1 - sincronizzare thread = farli partire assieme			check!
 *  2 - impostare manualmente la politica di schedulazione		check!
 *  3 - impostare l'affinità con un processore preciso			check!
 *  4 - rendere il numero di thread un parametro esterno		check!
 *  5 - controllare che la schedulazione funzioni correttamente	...
 *  6 - creare una funzione per la randomizzazione
 *  	(tanti thread con schedulazioni differenti)				...
 */

routine_param *par;//[THREADZ];
pthread_t *thread;//[THREADZ];

pthread_barrier_t barrier;

struct timeval start;

void fix_constant(int i, struct timeval start, int cpu_num, int how_many, int cpu_bound){
	par[i].start = start;
	par[i].cpu = cpu_num-1;
	par[i].values_num = how_many;
	par[i].values = (char *)malloc(how_many * sizeof(char));
	if (cpu_bound)
		par[i].cpu_bound = 1;
	else
		par[i].cpu_bound = 0;
	par[i].cid = i;
}

void printSched(int s){
	switch (s){
		case SCHED_OTHER :
			printf("OTHER | ");
			break;
		case SCHED_FIFO :
			printf("FIFO  | ");
			break;
		case SCHED_RR :
			printf("RR    | ");
			break;
		case SCHED_BATCH :
			printf("BATCH | ");
			break;
		case SCHED_IDLE :
			printf("IDLE  | ");
			break;
		default:
			break;
		}
}

int comparePar (const void * a, const void * b)
{
	((routine_param *)a)->time = ((routine_param *)a)->end.tv_sec+(((routine_param *)a)->end.tv_usec/1000000.0)-start.tv_sec+(start.tv_usec/1000000.0);
	((routine_param *)b)->time = ((routine_param *)b)->end.tv_sec+(((routine_param *)b)->end.tv_usec/1000000.0)-start.tv_sec+(start.tv_usec/1000000.0);
	if (((routine_param *)a)->time < ((routine_param *)b)->time)
		return -1;
	else if (((routine_param *)a)->time > ((routine_param *)b)->time)
		return 1;
	else
		return 0;
}

int main (int argc, char** argv) {

	// System setup
	int i = 0;
	int cpu_num = sysconf(_SC_NPROCESSORS_ONLN);
	int cpu_bound = 0;
	int thread_num = THREADZ;
	int schedulation = SCHED_FIFO;
	int how_many = REPEATER * 1;
	int randomize = 0;

	int var;
	char txt[255] = "";

	struct utsname os;


	// arguments: OSThreadz -i -t 5 -s 0-5 -m 1
	while ( (i = getopt(argc, argv, "it:s:m:r")) != -1){
		switch(i){
		case 'i':						// I/O selected
			cpu_bound = 1;
			break;
		case 't':
			var = atoi(optarg);			// preferred threads number
			if (var > 0 && var < 128)
				thread_num = var;
			break;
		case 's':						// preferred schedulation type
			var = atoi(optarg);
			if (var >= SCHED_OTHER && var != 4 && var <= 5)
				schedulation = var;
			break;
		case 'm' :
			var = atoi(optarg);
			if (var > 0)
				how_many = how_many * var;
			break;
		case 'r':
			thread_num=25;
			randomize = 1;
			break;
		case '?':
			printf("errore");
			break;
		default:
			break;
		}
	}

	par = malloc(sizeof(routine_param) * thread_num);
	thread = malloc(sizeof(pthread_t) * thread_num);

	srand(time(NULL));

	// print system specs
	uname(&os);
	printf("OS infos:     %s %s \n",os.sysname,os.release);
	printf("Architecture: %s \n",os.machine);
	printf("CPU Number:   %d \n\n", cpu_num);
	printf("Threads:      %d \n", thread_num);
	printf("Array Size:   %d \n", how_many);

	if (randomize){
		printf("Random execution.\n");
	}else {
		switch (schedulation){
		case SCHED_OTHER :
			printf("Schedulation: SCHED_OTHER\n");
			break;
		case SCHED_FIFO :
			printf("Schedulation: SCHED_FIFO\n");
			break;
		case SCHED_RR :
			printf("Schedulation: SCHED_RR\n");
			break;
		case SCHED_BATCH :
			printf("Schedulation: SCHED_BATCH\n");
			break;
		case SCHED_IDLE :
			printf("Schedulation: SCHED_IDLE\n");
			break;
		default:
			break;
		}
	}
	if (cpu_bound)
		printf("CPU-bound: no\n");
	else
		printf("CPU-bound: yes\n");

	printf("\n");

	fflush(stdout);

	// create routine structs

	if (randomize){
		// random mode
		i=0;
		// SCHED_FIFO
		for (; i<5; i++){
			fix_constant(i,start,cpu_num,how_many,cpu_bound);
			par[i].policy = SCHED_FIFO;
			par[i].cid = i;
			par[i].priority.__sched_priority = i*20 +1;
		}
		// SCHED_RR
		for (; i<10; i++){
			fix_constant(i,start,cpu_num,how_many,cpu_bound);
			par[i].policy = SCHED_RR;
			par[i].cid = i;
			par[i].priority.__sched_priority = (i-5)*20 +1;
		}
		// SCHED_OTHER
		for (; i<15; i++){
			fix_constant(i,start,cpu_num,how_many,cpu_bound);
			par[i].policy = SCHED_OTHER;
			par[i].cid = i;
			par[i].priority.__sched_priority = 0;
		}
		// SCHED_BATCH
		for (; i<20; i++){
			fix_constant(i,start,cpu_num,how_many,cpu_bound);
			par[i].policy = SCHED_BATCH;
			par[i].cid = i;
			par[i].priority.__sched_priority = 0;
		}
		// SCHED_IDLE
		for (; i<25; i++){
			fix_constant(i,start,cpu_num,how_many,cpu_bound);
			par[i].policy = SCHED_IDLE;
			par[i].cid = i;
			par[i].priority.__sched_priority = 0;
		}

	} else {
		// standard mode
		for (i=0; i<thread_num; i++){
			par[i].start = start;
			par[i].cpu = cpu_num-1;
			par[i].policy = schedulation;
			if (schedulation == SCHED_FIFO || schedulation == SCHED_RR)
				par[i].priority.__sched_priority = rand() % 99;
			else
				par[i].priority.__sched_priority = 0;
			par[i].values_num = how_many;
			par[i].values = (char *)malloc(how_many * sizeof(char));
			if (cpu_bound)
				par[i].cpu_bound = 1;
			else
				par[i].cpu_bound = 0;
			par[i].cid = i;
		}
	}

	gettimeofday(&start, NULL);

	// barrier initialization
	pthread_barrier_init(&barrier,NULL,thread_num);

	// threads creation
	for (i=0; i<thread_num; i++) {
		pthread_create(&thread[i], NULL, routine, (void *)&par[i]);
	}

	for (i=0; i<thread_num; i++) {
		pthread_join(thread[i], NULL);
	}

	for (i=0; i<thread_num; i++){
		par[i].start = start;
	}

	// result ordering
	qsort(par,thread_num,sizeof(routine_param),comparePar);

	// result print
	printf("  # |   Thread Id | CPU | Sched | Priority | Elapsed Time in seconds \n");
	for (i=0; i<thread_num; i++){
		printf("%3d |",par[i].cid);
		printf("%12d | ",par[i].tid);
		printf("%3d | ",(int)par[i].cpu_working);
		printSched(par[i].policy);
		printf("%8d | ", par[i].priority.__sched_priority);
		printf("%.10f",par[i].time);
		printf("\n");
	}

	// cleanup
	pthread_barrier_destroy(&barrier);

	for (i=0; i<thread_num; i++){
		if (par[i].cpu_bound){
			sprintf(txt,"thread%d.txt",par[i].tid);
			if(remove(txt) != 0 )
				printf("Error deleting file %s!\n",txt);
		}
	}

	return 0;
}
